package com.zeyun.EasyOnlineShareCloud.utils;

import cn.hutool.crypto.SecureUtil;
import com.zeyun.EasyOnlineShareCloud.common.convention.exception.ServiceException;
import com.zeyun.EasyOnlineShareCloud.dao.entity.FileDO;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * 文件工具类，负责文件的上传、下载和管理
 */
@Slf4j
@Component
public class FileUtil {
	// 常量定义
	private static final String ACCESS_CODE_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
	private static final int ACCESS_CODE_LENGTH = 4;
	private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd");
	private static final DateTimeFormatter PATH_DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy/MM/dd");
	private static final DateTimeFormatter ISO_DATE_TIME = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

	// 配置参数
	@Value("${com.file.upload-dir}")
	private String uploadDir;

	@Value("${server.address}")
	private String serverAddress;

	@Value("${server.port}")
	private int serverPort;

	@Value("${com.file.max-size}") // 默认100MB
	private long maxFileSize;

	@Value("${com.file.allowed-types}")
	private String allowedTypesString;

	private Set<String> allowedFileTypes;

	// 路径相关
	private Path uploadPath;
	private String baseUrl;

	/**
	 * 初始化方法，在Bean创建后自动执行
	 */
	@PostConstruct
	public void init() {
		try {
			// 初始化上传路径
			uploadPath =
					Paths.get(System.getProperty("user.dir"), "src/main/resources", uploadDir).toAbsolutePath().normalize();
			baseUrl = "http://" + serverAddress + ":" + serverPort + "/api/uploads/";

			// 确保上传目录存在
			Files.createDirectories(uploadPath);

			// 初始化允许的文件类型
			allowedFileTypes = new HashSet<>(Arrays.asList(allowedTypesString.split(",")));

			log.info("文件上传目录初始化完成: {}", uploadPath);
		} catch (IOException e) {
			log.error("创建上传目录失败", e);
			throw new ServiceException("初始化上传目录失败: " + e.getMessage());
		}
	}

	//================= 文件上传相关方法 =================//

	/**
	 * 上传文件并保存到数据库
	 *
	 * @param file       文件对象
	 * @param uploaderId 上传者ID
	 * @return 文件DO对象
	 */
	public FileDO uploadAndSaveFile(MultipartFile file, String uploaderId) {
		if (file == null || uploaderId == null) {
			throw new ServiceException("文件或上传者ID不能为空");
		}

		// 创建文件实体并生成ID
		FileDO fileDO = new FileDO();
		fileDO.setId(UUID.randomUUID().toString().replace("-", ""));

		// 解析文件名和扩展名
		String[] nameParts = parseFilename(file.getOriginalFilename());
		String fileExtension = nameParts[1];

		// 使用ID作为文件名
		String fileName = fileDO.getId() + fileExtension;

		// 上传文件
		Map<String, String> fileInfo = uploadFile(file, "files/" + getDatePath(), fileName);

		// 设置文件信息
		fileDO.setFileName(nameParts[0] + fileExtension);
		fileDO.setFileSize(file.getSize());
		fileDO.setFileType(determineFileType(file.getContentType()));
		fileDO.setFileExt(fileExtension.replace(".", ""));
		fileDO.setStoragePath(fileInfo.get("relativePath"));
		fileDO.setUploaderId(uploaderId);
		fileDO.setAccessCode(generateAccessCode());
		fileDO.setDownloadUrl(fileInfo.get("netFullUrl"));
		fileDO.setDownloadCount(0);
		fileDO.setUsedCount(0);
		fileDO.setAuditStatus("pending");
		fileDO.setStatus("active");
		fileDO.setUsageLimit(0);
		fileDO.setExpireTime(null);
		fileDO.setAuditTime(null);
		fileDO.setAuditUserId(null);
		fileDO.setAuditRemark(null);
		fileDO.setDelFlag(0);
		return fileDO;
	}

	/**
	 * 上传文件并返回文件信息
	 *
	 * @param file     文件对象
	 * @param subPath  子路径
	 * @param fileName 指定的文件名（如果为空则生成UUID文件名）
	 * @return 文件信息Map
	 */
	public Map<String, String> uploadFile(MultipartFile file, String subPath, String fileName) {
		// 验证文件有效性
		validateFile(file);
		try {
			// 解析文件名和扩展名
			String[] nameParts = parseFilename(file.getOriginalFilename());
			// 使用指定的文件名或生成唯一文件名（使用ID加原始后缀）
			String uniqueFileName = (fileName != null && !fileName.isEmpty()) ?
					fileName : generateUuidFileName(nameParts[1]);
			// 准备目标路径
			Path targetPath = prepareTargetPath(subPath, uniqueFileName);

			// 保存文件
			saveFile(file, targetPath);
			// 构建并返回文件信息
			return buildFileInfo(file, nameParts, uniqueFileName, targetPath);

		} catch (IOException e) {
			log.error("文件上传失败", e);
			throw new ServiceException("文件操作失败: " + e.getMessage());
		}
	}

	//================= 文件访问相关方法 =================//

	/**
	 * 根据访问码获取文件
	 *
	 * @param accessCode 访问码
	 * @param fileDO     文件实体对象
	 * @return 文件对象
	 */
	public File getFileByAccessCode(String accessCode, FileDO fileDO) {
		try {
			// 验证参数
			if (accessCode == null || accessCode.isEmpty()) {
				throw new ServiceException("访问码不能为空");
			}

			// 验证文件状态
			if (fileDO == null) {
				throw new ServiceException("文件信息不存在");
			}

			if (!("active").equals(fileDO.getStatus())) {
				throw new ServiceException("文件已过期或无效");
			}

			// 验证审核状态
			/* if (!("approved").equals(fileDO.getAuditStatus())) {
				throw new ServiceException("文件未通过审核");
			} */

			// 验证访问码
			if (!accessCode.equals(fileDO.getAccessCode())) {
				throw new ServiceException("访问码无效");
			}

			// 验证过期时间
			if (fileDO.getExpireTime() != null && fileDO.getExpireTime().before(new Date())) {
				fileDO.setStatus("expired");
				throw new ServiceException("文件已过期");
			}

			// 验证使用次数
			if (fileDO.getUsageLimit() > 0 && fileDO.getUsedCount() >= fileDO.getUsageLimit()) {
				fileDO.setStatus("used");
				throw new ServiceException("文件已达到使用次数限制");
			}

			// 验证文件是否存在 - 处理相对路径
			Path filePath;
			if (Paths.get(fileDO.getStoragePath()).isAbsolute()) {
				// 如果是绝对路径，直接使用
				filePath = Paths.get(fileDO.getStoragePath());
			} else {
				// 如果是相对路径，则基于uploadPath解析
				filePath = uploadPath.resolve(fileDO.getStoragePath());
			}

			File file = filePath.toFile();
			if (!file.exists()) {
				throw new ServiceException("文件不存在");
			}

			return file;
		} catch (ServiceException e) {
			throw e;
		} catch (Exception e) {
			log.error("获取文件失败", e);
			throw new ServiceException("文件访问失败");
		}
	}

	/**
	 * 读取文件资源
	 *
	 * @param filePath 文件绝对路径
	 * @return File
	 */
	public File readFileResource(String filePath) {
		if (filePath == null || filePath.isEmpty()) {
			throw new ServiceException("文件路径不能为空");
		}

		try {
			Path path = Paths.get(filePath).normalize();
			if (!path.toFile().exists()) {
				throw new ServiceException("文件不存在: " + filePath);
			}

			// 安全检查：确保请求的文件在允许的目录内
			if (!path.startsWith(uploadPath)) {
				throw new SecurityException("无权访问该文件路径");
			}

			return path.toFile();
		} catch (SecurityException e) {
			log.error("文件安全检查失败", e);
			throw new ServiceException("安全限制：" + e.getMessage());
		} catch (Exception e) {
			log.error("文件读取失败", e);
			throw new ServiceException("文件读取失败: " + e.getMessage());
		}
	}

	//================= 文件删除相关方法 =================//

	/**
	 * 物理删除文件
	 *
	 * @param filePath 文件路径
	 * @return 是否删除成功
	 */
	public boolean deleteFile(String filePath) {
		try {
			if (filePath == null || filePath.isEmpty()) {
				log.warn("删除文件失败: 文件路径为空");
				return false;
			}

			File file = new File(filePath);
			if (file.exists()) {
				boolean deleted = file.delete();
				if (!deleted) {
					log.warn("删除文件失败: {}", filePath);
					return false;
				}
				log.info("文件已删除: {}", filePath);
				return true;
			}
			log.info("文件不存在，无需删除: {}", filePath);
			return true;
		} catch (Exception e) {
			log.error("删除文件异常: {}", filePath, e);
			return false;
		}
	}

	/**
	 * 清理临时文件
	 *
	 * @param file 需要清理的文件
	 */
	public void cleanupTempFile(File file) {
		if (file != null && file.exists()) {
			try {
				boolean deleted = file.delete();
				if (!deleted) {
					log.warn("临时文件删除失败: {}", file.getAbsolutePath());
				}
			} catch (Exception e) {
				log.error("删除临时文件失败", e);
			}
		}
	}

	/**
	 * 检查并清理过期文件
	 * 可以通过定时任务调用该方法
	 *
	 * @param expiredFiles 过期文件列表
	 */
	public void cleanupExpiredFiles(List<FileDO> expiredFiles) {
		if (expiredFiles == null || expiredFiles.isEmpty()) {
			return;
		}

		for (FileDO file : expiredFiles) {
			try {
				if (deleteFile(file.getStoragePath())) {
					log.info("成功删除过期文件: {}", file.getFileName());
				} else {
					log.warn("删除过期文件失败: {}", file.getFileName());
				}
			} catch (Exception e) {
				log.error("清理过期文件时发生错误", e);
			}
		}
	}

	//================= 工具方法 =================//

	/**
	 * 生成随机访问码
	 *
	 * @return 4位随机字母数字组合
	 */
	public String generateAccessCode() {
		StringBuilder code = new StringBuilder(ACCESS_CODE_LENGTH);
		Random random = new Random();
		for (int i = 0; i < ACCESS_CODE_LENGTH; i++) {
			int index = random.nextInt(ACCESS_CODE_CHARS.length());
			code.append(ACCESS_CODE_CHARS.charAt(index));
		}
		String accessCode = code.toString();
		log.info("生成访问码: {}", accessCode);
		return accessCode;
	}

	/**
	 * 生成文件名（使用ID加原始后缀）
	 *
	 * @param extension 文件扩展名（包含点号）
	 * @return 生成的文件名（包含扩展名）
	 */
	private String generateUuidFileName(String extension) {
		// 生成不含分隔符的UUID作为ID
		String id = UUID.randomUUID().toString().replace("-", "");
		// 添加日期前缀作为文件夹，文件名直接使用ID加原始后缀
		return id + extension;
	}

	/**
	 * 获取日期路径，用于文件分目录存储
	 *
	 * @return 格式化的日期路径
	 */
	private String getDatePath() {
		return LocalDateTime.now().format(PATH_DATE_FORMATTER);
	}

	/**
	 * 解析文件名，分离文件名和扩展名
	 *
	 * @param filename 原始文件名
	 * @return 包含文件名和扩展名的数组
	 */
	private String[] parseFilename(String filename) {
		if (filename == null || filename.isEmpty()) {
			return new String[]{"", ""};
		}

		int dotIndex = filename.lastIndexOf('.');
		return (dotIndex == -1) ?
				new String[]{filename, ""} :
				new String[]{filename.substring(0, dotIndex), filename.substring(dotIndex)};
	}

	/**
	 * 准备目标路径
	 *
	 * @param subPath  子路径
	 * @param filename 文件名
	 * @return 完整的目标路径
	 * @throws IOException 如果创建目录失败
	 */
	private Path prepareTargetPath(String subPath, String filename) throws IOException {
		// 安全处理子路径
		Path normalizedSubPath = Paths.get(subPath == null ? "" : subPath).normalize();
		if (normalizedSubPath.isAbsolute() || normalizedSubPath.startsWith("..")) {
			throw new SecurityException("不允许使用绝对路径或上级目录路径");
		}

		// 创建目标目录
		Path targetDir = uploadPath.resolve(normalizedSubPath);
		Files.createDirectories(targetDir);

		// 返回完整文件路径
		return targetDir.resolve(filename);
	}

	/**
	 * 保存文件到目标路径
	 *
	 * @param file       文件对象
	 * @param targetPath 目标路径
	 * @throws IOException 如果保存失败
	 */
	private void saveFile(MultipartFile file, Path targetPath) throws IOException {
		// 使用流式处理，避免大文件内存问题
		try (InputStream inputStream = file.getInputStream()) {
			Files.copy(inputStream, targetPath, StandardCopyOption.REPLACE_EXISTING);
		}
	}

	/**
	 * 构建文件信息
	 *
	 * @param file           文件对象
	 * @param nameParts      文件名部分
	 * @param uniqueFileName 唯一文件名
	 * @param targetPath     目标路径
	 * @return 文件信息Map
	 * @throws IOException 如果读取文件失败
	 */
	private Map<String, String> buildFileInfo(MultipartFile file, String[] nameParts,
											  String uniqueFileName, Path targetPath) throws IOException {
		Map<String, String> info = new LinkedHashMap<>();

		// 基本信息
		info.put("originalName", nameParts[0]);
		info.put("storageName", uniqueFileName);
		info.put("fileSize", formatSize(file.getSize()));
		info.put("fileType", file.getContentType());
		info.put("uploadTime", LocalDateTime.now().format(ISO_DATE_TIME));
		info.put("extension", nameParts[1]);

		// 计算MD5（流式处理，避免大文件内存问题）
		String md5;
		try (InputStream is = Files.newInputStream(targetPath)) {
			md5 = SecureUtil.md5().digestHex(is);
		}
		info.put("md5Hash", md5);

		// 路径信息
		Path relativePath = uploadPath.relativize(targetPath);
		String normalizedRelativePath = relativePath.toString().replace("\\", "/");
		info.put("relativePath", normalizedRelativePath);
		info.put("absolutePath", targetPath.toString());
		info.put("netFullUrl", baseUrl + normalizedRelativePath);

		return info;
	}

	/**
	 * 验证文件有效性
	 *
	 * @param file 文件对象
	 * @throws ServiceException 如果文件无效
	 */
	private void validateFile(MultipartFile file) {
		// 检查文件是否为空
		if (file == null || file.isEmpty()) {
			throw new ServiceException("文件不能为空");
		}

		// 检查文件名
		if (file.getOriginalFilename() == null || file.getOriginalFilename().isEmpty()) {
			throw new ServiceException("文件名不能为空");
		}

		// 文件大小验证
		if (file.getSize() > maxFileSize) {
			throw new ServiceException("文件超过最大允许大小: " + formatSize(maxFileSize));
		}

		// 文件类型验证
		String contentType = file.getContentType();
		if (contentType == null) {
			throw new ServiceException("无法确定文件类型");
		}

		// 检查是否属于允许的文件类型
		boolean allowed = false;
		for (String allowedType : allowedFileTypes) {
			if (allowedType.endsWith("/*")) {
				// 如果是通配符类型，如image/*
				String prefix = allowedType.substring(0, allowedType.indexOf("/*"));
				if (contentType.startsWith(prefix)) {
					allowed = true;
					break;
				}
			} else if (contentType.equals(allowedType)) {
				allowed = true;
				break;
			}
		}

		// 如果不允许，检查文件扩展名是否为压缩文件
		if (!allowed && file.getOriginalFilename() != null) {
			String fileName = file.getOriginalFilename();
			if (isArchiveFile(fileName) && allowedFileTypes.contains("application/zip")) {
				allowed = true;
			}
		}

		if (!allowed) {
			throw new ServiceException("不支持的文件类型: " + contentType);
		}
	}

	/**
	 * 格式化文件大小
	 *
	 * @param bytes 字节数
	 * @return 格式化后的大小字符串
	 */
	private String formatSize(long bytes) {
		if (bytes < 1024) return bytes + " B";
		int exp = (int) (Math.log(bytes) / Math.log(1024));
		return String.format("%.1f %sB", bytes / Math.pow(1024, exp), "KMGTPE".charAt(exp - 1));
	}

	/**
	 * 确定文件类型分类
	 *
	 * @param mimeType MIME类型
	 * @return 文件类型分类
	 */
	private String determineFileType(String mimeType) {
		if (mimeType == null) return "other";

		if (mimeType.startsWith("image/")) {
			return "image";
		} else if (mimeType.startsWith("video/")) {
			return "video";
		} else if (mimeType.startsWith("audio/")) {
			return "audio";
		} else if (mimeType.startsWith("text/") ||
				mimeType.contains("document") ||
				mimeType.contains("pdf")) {
			return "document";
		} else if (mimeType.contains("zip") ||
				mimeType.equals("application/zip") ||
				mimeType.contains("compressed") ||
				mimeType.contains("archive") ||
				mimeType.contains("application/x-rar") ||
				mimeType.contains("application/x-7z") ||
				mimeType.contains("application/x-tar") ||
				mimeType.contains("application/gzip")) {
			return "archive";
		} else {
			// 检查文件MIME类型无法识别的情况下，尝试通过文件扩展名判断
			if (mimeType.contains(".zip") || mimeType.contains(".rar") || 
				mimeType.contains(".7z") || mimeType.contains(".tar") || 
				mimeType.contains(".gz") || mimeType.contains(".gzip")) {
				return "archive";
			} else if (mimeType.contains(".mp3") || mimeType.contains(".wav") || 
				mimeType.contains(".ogg") || mimeType.contains(".flac") || 
				mimeType.contains(".aac") || mimeType.contains(".m4a")) {
				return "audio";
			} else if (mimeType.contains(".mp4") || mimeType.contains(".avi") || 
				mimeType.contains(".mov") || mimeType.contains(".wmv") || 
				mimeType.contains(".mkv") || mimeType.contains(".webm")) {
				return "video";
			}
			return "other";
		}
	}

	/**
	 * 确定文件的内容类型
	 *
	 * @param fileExt 文件扩展名
	 * @return 内容类型
	 */
	private String determineContentType(String fileExt) {
		if (fileExt == null || fileExt.isEmpty()) {
			return "application/octet-stream";
		}

		return switch (fileExt.toLowerCase()) {
			case "pdf" -> "application/pdf";
			case "doc" -> "application/msword";
			case "docx" -> "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
			case "xls" -> "application/vnd.ms-excel";
			case "xlsx" -> "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
			case "png" -> "image/png";
			case "jpg", "jpeg" -> "image/jpeg";
			case "gif" -> "image/gif";
			case "txt" -> "text/plain";
			case "html" -> "text/html";
			// 压缩文件类型
			case "zip" -> "application/zip";
			case "rar" -> "application/x-rar-compressed";
			case "7z" -> "application/x-7z-compressed";
			case "tar" -> "application/x-tar";
			case "gz", "gzip" -> "application/gzip";
			case "jar" -> "application/java-archive";
			case "bz2" -> "application/x-bzip2";
			case "xz" -> "application/x-xz";
			case "tgz" -> "application/gzip";
			default -> "application/octet-stream";
		};
	}

	/**
	 * 检查文件是否为压缩包类型
	 *
	 * @param fileName 文件名
	 * @return 是否为压缩包
	 */
	public boolean isArchiveFile(String fileName) {
		if (fileName == null || fileName.isEmpty()) {
			return false;
		}
		
		String ext = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
		return ext.equals("zip") || ext.equals("rar") || ext.equals("7z") || 
			ext.equals("tar") || ext.equals("gz") || ext.equals("gzip") ||
			ext.equals("jar") || ext.equals("bz2") || ext.equals("xz") ||
			ext.equals("tgz");
	}
}